program ChromeRevisionTrace;

include <core.vhs>
include <shell.vhs>
include "chrome.vhs"

import core::*;
import core::Pattern::*;
import core::Logging::*;
import shell::*;

function main(maxentry='20000000') {
	Logging::configureLog("versiondetector.log");
	log("Start vulnerable revision to version mapping...");
	chrome::initVersionInformation();
	var out = new CsvFile('output/bug-versions.csv');
	out.writeln('Bug,OrgRev,OrgLine,LastVulnRev,VulnVer,VulnVerLine,Loc');
	var outfunc = function(data) {
		out.writeCsvLine([data.bug, data.orgRev, data.orgLine, data.lastVulnRev, data.vulnVer, data.vulnVerLine, data.loc]);
	};
	VulnerableRevisionEntry(input = new SequenceStream('output/bug-changelines.csv', 4096), maxOccur=Runtime.asInt(maxentry), outfunc=outfunc);
	out.close();
	log("Completed.");
}
//76666|77507|78031
pattern<DataPart> VulnerableRevisionEntry($dataPattern=@'^(\d+),(\d+),(\d+),(\d+),([^,]*),([^\n]*)\n', $flag=MULTILINES, $outfunc=null) {
	var orgRev = Runtime.asInt(this[3]);
	var vulnRev = Runtime.asInt(this[2]);
	var path = this[5] + '';
	var loc = this[6] + '';
	var line = this[4] + '';
	var bug = this[1] + '';
	var verRev;
	var FSF = chrome::locateInCodebase(path);
	var blameF;
	var foundLine;
	
	log('Process bug %s, orgRev: %d, line: %s, vulnRev: %d, file: %s', bug, orgRev, line, vulnRev, path);
	
	if (FSF == null)
		log_warning('\tPath not found in codebases');
	// scan through all versions 
	var hasCandidate = false;
	var message=format('Searching...\n');
	for (var ver: chrome::getReleasedVersions()) {		
		//println('\tChecking version %s', ver);
		verRev = chrome::ver2rev(ver);
		if (verRev >= orgRev) { // && verRev <= vulnRev) {			
			hasCandidate = true;
			message = message + format('\t\t%s(%d)', ver, verRev);
			if (orgRev == -1) {
				outfunc?(struct { bug=bug, orgRev=orgRev, orgLine=line, lastVulnRev=vulnRev, vulnVer=ver, vulnVerLine=-1, loc='' });
				message = message + ':NL;' ;
				print('-');
			}
			else {
				blameF = null;
				if (verRev ==0) {
					var newFSF = chrome::BASE_LOCATION + ver + '/src/' + path;
					if (fileExists(newFSF))
						blameF = chrome::blameRevision(path, FSF=newFSF, version=ver);
				}
				else
					blameF = chrome::blameRevision(path, verRev, FSF=FSF);
				
				if (blameF != null) {
					foundLine = locateLine(orgRev=orgRev, loc=loc, input = new SequenceStream(blameF, 4096));
					if (foundLine > -1) {
						message = message + format('[%d]; ', foundLine);
						print('x');
						outfunc?(struct { bug=bug, orgRev=orgRev, orgLine=line, lastVulnRev=vulnRev, vulnVer=ver, vulnVerLine=foundLine, loc=loc });
					}
					else {
						print('.');
						message = message + '[NF];';
					}
				}
				else {
					message = message + '[FNF];';
					print('.');
				}
			}
			message = message + '\n';
		}
	}
	if (!hasCandidate)
		message = 'Version not fount [Revision OutOfRange].\n'
	println('');
	log(message);
}

pattern locateLine($orgRev, $loc) {
	var lineCount = 0;
	var line;
	var terminated = false;
	var result = -1;
	while (!terminated && result == -1) {
		line = chrome::AnnotatedLine();
		if (isMatchFail(line))
			terminated = true;
		else {
			lineCount++;
			if (orgRev == Runtime.asInt(line[0]) && loc == line[2]) 
				result = lineCount;
		}
	}
	return result;
}
